import unittest
from StringIO import StringIO
from limber import componentparser
from limber.componentparser import Token, ComponentParserError


def testInstantiate():
    try:
        parser = componentparser.ComponentParser()
    except Exception,e:
        assert False, 'Could not instantiate the parser: %r.' % e


class TestComponentParser(unittest.TestCase):

    def setUp(self):
        self.parser = componentparser.ComponentParser()

    def testLogicalLines(self):
        text = 'author = A:\\\n'
        text += '    B:\\\n'
        text += '    C  :\\\n'
        text += '     :   D:\\\n'

        text = StringIO(text)

        lines = ['author = A:B:C  ::   D:\n']
        self.assertEqual(list(self.parser._logical_lines(text)), lines)

        text = StringIO('author = A:')
        lines = ['author = A:\n']
        self.assertEqual(list(self.parser._logical_lines(text)), lines)

    def testMultipleLogicalLines(self):
        text = 'author = A:\n'
        text += 'abilities = B:\\\n'
        text += '   : C:\\\n'
        text += '   :D  :E:F :\\\n'
        text += 'license = MIT\n'
        text = StringIO(text)

        lines = [
            'author = A:\n',
            'abilities = B:: C::D  :E:F :\n',
            'license = MIT\n'
            ]
        self.assertEqual(list(self.parser._logical_lines(text)), lines)

    def testLogicalLinesIgnoreEmptyLines(self):
        text = 'author = A:\n'
        text += '\n'
        text += '  \t  \n'
        text += 'license = MIT\n'
        text += 'abilities = B:\\\n'
        text += '   \t  \\\n'
        text += '   : C :  D :\\\n'
        text += '   :E: F:\n'
        text += 'author_email = prova\n'
        text = StringIO(text)

        lines = [
            'author = A:\n',
            'license = MIT\n',
            'abilities = B:: C :  D ::E: F:\n',
            'author_email = prova\n'
            ]
        self.assertEqual(list(self.parser._logical_lines(text)), lines)

    def testLogicalLinesRaisesComponentParserError(self):
        text = 'author = A:\n'
        text += 'license = B: C:\\\n'
        text += '    \t\n'
        text += '   D: E: \\\n'
        text = StringIO(text)
        logical_lines = (lambda text: list(self.parser._logical_lines(text)))
        self.assertRaises(ComponentParserError,
                          logical_lines,
                          text)

    def testCleanList(self):
        text = 'A ::B  : C:: D:'
        result = 'A :B  : C: D'
        self.assertEqual(self.parser._clean_list(text), result)

    def testCleanValue(self):
        text = 'Val:::'
        result = 'Val'
        self.assertEqual(self.parser._clean_value(text), result)

    def testTokenize(self):
        text = StringIO('name = Test no. 1\n')
        tokens = [
            Token('name', Token.IDENTIFIER),
            Token('=', Token.ASSIGNMENT),
            Token('Test no. 1', Token.VALUE),
            ]

        self.assertEqual(list(self.parser.tokenize(text)), tokens)

    def testTokenizeLists(self):
        text = StringIO('abilities = A:B:C:D\n')
        tokens = [
            Token('abilities', Token.IDENTIFIER),
            Token('=', Token.ASSIGNMENT),
            Token('A:B:C:D', Token.VALUE)
            ]
        values = list('ABCD')

        result_tokens = list(self.parser.tokenize(text))
        self.assertEqual(result_tokens, tokens)
        self.assertTrue(result_tokens[-1].is_list(),
                        'List values are not recognized.')

        self.assertEqual(result_tokens[-1].values(), values)

    def testTokenizeListsWithSpaces(self):
        text = StringIO('author = A : B  :C:  D : E')
        tokens = [
            Token('author', Token.IDENTIFIER),
            Token('=', Token.ASSIGNMENT),
            Token('A : B  :C:  D : E', Token.VALUE)
            ]
        values = [
            'A ',
            ' B  ',
            'C',
            '  D ',
            ' E'
            ]

        result_tokens = list(self.parser.tokenize(text))
        print tokens
        print result_tokens
        self.assertEqual(result_tokens, tokens)
        self.assertTrue(result_tokens[-1].is_list(),
                        'List with spaces is not recognized.')

        self.assertEqual(result_tokens[-1].values(), values)

    def testTokenizeMultilineLists(self):
        text =  'author = A:\\\n'
        text += '    B :\\\n'
        text += '    C:\\\n'
        text += '  D :\\\n'
        text += '  E \n'
        text = StringIO(text)

        tokens = [
            Token('author', Token.IDENTIFIER),
            Token('=', Token.ASSIGNMENT),
            Token('A:B :C:D :E', Token.VALUE)
            ]

        values = [
            'A',
            'B ',
            'C',
            'D ',
            'E'
            ]

        result_tokens = list(self.parser.tokenize(text))
        self.assertEqual(result_tokens, tokens)
        self.assertTrue(result_tokens[-1].is_list(),
                        'Multiline list not recognized.')

        self.assertEqual(result_tokens[-1].values(), values)

    def testTokenizeEmptyElements(self):
        text = StringIO('author = C:')

        tokens = [
            Token('author', Token.IDENTIFIER),
            Token('=', Token.ASSIGNMENT),
            Token('C', Token.VALUE)
            ]

        result_tokens = list(self.parser.tokenize(text))
        self.assertEqual(result_tokens, tokens)
        self.assertFalse(result_tokens[-1].is_list())

        text = StringIO('author = : C')

        tokens[-1] = Token(' C', Token.VALUE)

        result_tokens = list(self.parser.tokenize(text))
        self.assertEqual(result_tokens, tokens)
        self.assertFalse(result_tokens[-1].is_list())

        text =  'author = : C :\\\n'
        text += '    : D :\\\n'
        text += ' :E:\\\n'
        text += '  : : F:\\\n'
        text += '  G :\n'
        text = StringIO(text)

        tokens = [
            Token('author', Token.IDENTIFIER),
            Token('=', Token.ASSIGNMENT),
            Token(' C : D :E: F:G ', Token.VALUE)
            ]

        result_tokens = list(self.parser.tokenize(text))
        self.assertEqual(result_tokens, tokens)
        self.assertTrue(result_tokens[-1].is_list())

    def testValidateValue(self):
        value = 'A'
        self.assertEqual(self.parser._validate_value('author', value), value)

        value = 'A:B'
        self.assertEqual(self.parser._validate_value('abilities', value), ['A', 'B'])

        value = '0.1.0'
        self.assertEqual(self.parser._validate_value('version', value), value)

    def testValidateValueRaisesError(self):
        value = 'A:B'
        self.assertRaises(ComponentParserError,
                          self.parser._validate_value, 'full_name', value)

        value = 'something'
        self.assertRaises(ComponentParserError,
                          self.parser._validate_value, 'interface', value)

    def testParseInfos(self):
        text =  StringIO('author = A\nlicense = MIT\n')

        expected_infos = {
            'author' : 'A',
            'license' : 'MIT'
            }

        self.assertEqual(dict(self.parser.parse_infos(text)),  expected_infos)

    def testParseInfosLists(self):
        text = StringIO('abilities = A:B:C\n')
        expected_infos = {
            'abilities' : ['A', 'B', 'C'],
            }
        self.assertEqual(dict(self.parser.parse_infos(text)),  expected_infos)

    def testParseInfosMultilineLists(self):
        text = StringIO('abilities = A:\\\nB:\\\nC\n')
        expected_infos = {
            'abilities' : ['A', 'B', 'C']
            }

        self.assertEqual(dict(self.parser.parse_infos(text)), expected_infos)

        text = StringIO('abilities = A :\\\n   : B: C  :\\\n  D: E:\n')
        expected_infos = {
            'abilities' : ['A ', ' B', ' C  ', 'D', ' E']
            }
        self.assertEqual(dict(self.parser.parse_infos(text)), expected_infos)

    def testParseRaisesComponentErrorCloneIdentfiers(self):
        text = StringIO('abilities = A\nabilities = B\n')
        self.assertRaises(ComponentParserError,
                          self.parser.parse, text)

        text = StringIO('abilities = A:B\nauthor = C\nabilities = E:F\n')
        self.assertRaises(ComponentParserError,
                          self.parser.parse, text)

    def testParseRaisesComponentErrorInvalidFile(self):
        text = StringIO('author = A\n')
        self.assertRaises(ComponentParserError,
                          self.parser.parse, text)

    def testParseMinimalFile(self):
        text = StringIO('''\
name = AAA
version = 0.1.0
author = B
directory = C
filename = D
abilities = E:F
interface = document
short_description = G
''')
        infos = self.parser.parse(text)
        self.assertEqual(infos.name, 'AAA')
        self.assertEqual(infos.version, '0.1.0')
        self.assertEqual(infos.author, 'B')
        self.assertEqual(infos.directory, 'C')
        self.assertEqual(infos.filename, 'D')
        self.assertEqual(infos.abilities, ['E', 'F'])
        self.assertEqual(infos.interface, 'document')
        self.assertEqual(infos.short_description, 'G')
        self.assertEqual(infos.full_name, 'AAA',
                         'full_name should default to name.')

    def testParse(self):
        info_dict = {
            'name' : 'PlainTextView',
            'version' : '0.3.11rc',
            'author' : 'Alzetta Giacomo',
            'author_email' : 'giacomo.alzetta@gmail.com',
            'directory' : '/home/TomBombadil',
            'filename' : 'The_Ring_to_Rule_Them_All',
            'abilities' : ['Become Invisible', 'Fall into darkness'],
            'interface' : 'view',
            'short_description' : 'A simple view for plain text.',
            'license' : 'GPL v3',
            'website' : 'http://www.limber.plaintextview.com'
            }
        other_dict = dict(info_dict)
        other_dict['abilities'] = ':'.join(info_dict['abilities'])

        text = StringIO('''\
name = %(name)s
version = %(version)s
author = %(author)s
author_email = %(author_email)s
directory = %(directory)s
filename = %(filename)s
abilities = %(abilities)s
interface = %(interface)s
short_description = %(short_description)s
license = %(license)s
website = %(website)s
'''
        % other_dict)

        got = self.parser.parse(text)
        for info, val in info_dict.items():
            self.assertEqual(getattr(got, info), val)
    
    def testParseMustHaveLists(self):
        info_dict = {
            'name' : 'PlainTextView',
            'version' : '0.3.11rc',
            'author' : 'Alzetta Giacomo',
            'author_email' : 'giacomo.alzetta@gmail.com',
            'directory' : '/home/TomBombadil',
            'filename' : 'The_Ring_to_Rule_Them_All',
            'abilities' : ['Become Invisible'],
            'interface' : 'view',
            'short_description' : 'A simple view for plain text.',
            'license' : 'GPL v3',
            'website' : 'http://www.limber.plaintextview.com'
            }
        other_dict = dict(info_dict)
        other_dict['abilities'] = ':'.join(info_dict['abilities'])

        text = StringIO('''\
name = %(name)s
version = %(version)s
author = %(author)s
author_email = %(author_email)s
directory = %(directory)s
filename = %(filename)s
abilities = %(abilities)s
interface = %(interface)s
short_description = %(short_description)s
license = %(license)s
website = %(website)s
'''
        % other_dict)

        got = self.parser.parse(text)
        for info, val in info_dict.items():
            self.assertEqual(getattr(got, info), val)
    
    def testValidateAlnum(self):
        validator = self.parser._validate_alnum(3)
        self.assertTrue(validator('alphanumeric_string-with-hyphen_and_underscore9'))
        self.assertFalse(validator('string with spaces'))
        self.assertFalse(validator('a'), 'String of length 1 should not be valid.')

    def testValidateVersion(self):
        validator = self.parser._validate_version
        self.assertTrue(validator('0.1.0'))
        self.assertTrue(validator('1.34.5a'))
        self.assertTrue(validator('12.4.7b'))
        self.assertTrue(validator('1.2.3rc'))
        self.assertTrue(validator('6.5.4dev'))

        self.assertFalse(validator('No0.1.0'))
        self.assertFalse(validator('0.1.0other'))
        self.assertFalse(validator('1.a.23a'))

    def testValidateFilename(self):
        validator = self.parser._validate_filename
        self.assertTrue(validator('valid_filename.txt'))
        self.assertTrue(validator(['list', 'of.1-valid', 'filenames.com']))

        self.assertFalse(validator('invalid_filename with spaces.txt'))
        self.assertFalse(validator('invalid_filename @ at'))

    def testValidateInterface(self):
        validator = self.parser._validate_interface
        self.assertTrue(validator('document'))
        self.assertTrue(validator('view'))

        self.assertFalse(validator('otherstring'))
        self.assertFalse(validator('lol-rofl'))

    def testValidateUrl(self):
        validator = self.parser._validate_url

        self.assertTrue(validator('http://www.google.com'))
        self.assertTrue(validator('http://www.google.com#query=lol?'))
        self.assertTrue(validator('http://www.lol.it%12'))

        self.assertFalse(validator('http://{}'))
        self.assertFalse(validator('lolling<'))

    def testValidateClassName(self):
        validator = self.parser._validate_class_name

        self.assertTrue(validator('ValidClassName'))
        self.assertTrue(validator('Valid_Class_Name'))
        self.assertTrue(validator('ValidClassName89'))
        self.assertTrue(validator('Valid89ClassName'))
        self.assertTrue(validator('Valid_Class89_Name92'))

        self.assertFalse(validator('Invalid-Class_Name'))
        self.assertFalse(validator('Invalid@Class_Name'))
        self.assertFalse(validator('Invalid Class Name'))
        for o in range(127):
            char = chr(o)
            if char.isalnum() or char == '_':
                continue
            invalid_name = 'Invalid %s Class Name' % char
            self.assertFalse(validator(invalid_name))


class TestComponentInfos(unittest.TestCase):
    
    def testInstantiate(self):
        infos = componentparser.ComponentInfos(
            'name',
            '0.1.0', 
            'short_description',
            'author', 
            'directory',
            'filename',
            ['TEXT'], 
            'document')
        
        self.assertEqual(infos.full_name, infos.name)
        self.assertEqual(infos.revision_version, infos.version)
        self.assertEqual(infos.class_name, infos.name)
        
        self.assertRaises(TypeError,
                          componentparser.ComponentInfos,
                          'name', '0.1.0'
                          )
        
        infos = componentparser.ComponentInfos(
            'name',
            '0.1.0', 
            'short_description',
            'author', 
            'directory',
            'filename',
            ['TEXT'], 
            'document', 
            full_name='FullName',
            revision_version='235r',
            class_name='TheClass')
        
        self.assertNotEqual(infos.full_name, infos.name)
        self.assertNotEqual(infos.class_name, infos.name)
        self.assertNotEqual(infos.revision_version, infos.version)
        self.assertNotEqual(infos.class_name, infos.full_name)
    
    def testEquality(self):
        args1 = (
            'name',
            '0.1.0', 
            'short_description',
            'author', 
            'directory',
            'filename',
            ['TEXT'], 
            'document',
            )
        kwargs1 = {}
        
        args2 = (
            'name',
            '0.1.0', 
            'short_description',
            'author', 
            'directory',
            'filename',
            ['TEXT'], 
            'document',
            )
        
        kwargs2 = dict(
            full_name='FullName',
            revision_version='235r',
            class_name='TheClass'
            )
        
        infos1 = componentparser.ComponentInfos(*args1, **kwargs1)
        infos2 = componentparser.ComponentInfos(*args1, **kwargs1)
        infos3 = componentparser.ComponentInfos(*args2, **kwargs2)
        
        self.assertEqual(infos1, infos2)
        self.assertNotEqual(infos1, infos3)
        self.assertNotEqual(infos2, infos3)
